﻿using System.Collections.Specialized;
using System.Dynamic;
using System.Web.Routing;

namespace LaoLuo
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    /// <summary>
    /// 共用的扩展方法
    /// </summary>
    public static class CommonExtensions
    {
        private static Dictionary<string, DayOfWeek> _dayOfWeekChinese;
        private static Dictionary<string, DayOfWeek> DayOfWeekChinese
        {
            get
            {
                return _dayOfWeekChinese ?? (_dayOfWeekChinese = new Dictionary<string, DayOfWeek>
                                                                     {
                                                                         {"一", DayOfWeek.Monday},
                                                                         {"二", DayOfWeek.Tuesday},
                                                                         {"三", DayOfWeek.Wednesday},
                                                                         {"四", DayOfWeek.Thursday},
                                                                         {"五", DayOfWeek.Friday},
                                                                         {"六", DayOfWeek.Saturday},
                                                                         {"日", DayOfWeek.Sunday},
                                                                         {"天", DayOfWeek.Sunday}
                                                                     });
            }
        }

        /// <summary>
        /// gets a bool value indicates whether current value is in specified array
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static bool IsIn<T>(this T value, params T[] args)
        {
            return args.Any(i => i.Equals(value));
        }

        public static ExpandoObject ToExpando(this object anonymousObject)
        {
            IDictionary<string, object> anonymousDictionary = new RouteValueDictionary(anonymousObject);
            IDictionary<string, object> expando = new ExpandoObject();
            foreach (var item in anonymousDictionary)
                expando.Add(item);
            return (ExpandoObject)expando;
        }


        /// <summary>
        /// return an clone version of specified object
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public static T Clone<T>(this T source)
        {
            return source.ToXml().FromXml<T>();
        }


        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dictionary"></param>
        /// <returns></returns>
        public static T ToObject<T>(this IDictionary<string, object> dictionary)
            where T : new()
        {
            var t = typeof (T);
            var ret = new T();

            foreach (var key in dictionary.Keys)
            {
                var prop = t.GetProperty(key);
                prop.SetValue(ret, dictionary[key], null);
            }

            return ret;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="objectsToMerge"></param>
        /// <returns></returns>
        public static T Extend<T>(this T obj, params T[] objectsToMerge)
            where T : new()
        {
            if (objectsToMerge.Length == 0)
            {
                throw new ArgumentException("must specify objectsToMerge", "objectsToMerge");
            }

            return objectsToMerge.Aggregate(obj, (current, o) => current.Extend(o));
        }

        /// <summary>
        /// get a new merged object from <see cref="objectToMerge"/>. caution: the return value is not the same reference as original <see cref="obj"/>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="objectToMerge"></param>
        /// <returns></returns>
        public static T Extend<T>(this T obj, T objectToMerge)
            where T : new()
        {
            var objCopy = obj.Clone();

            foreach (var propertyInfo in objCopy.GetPropertiesInstancePublic())
            {
                var toValue = propertyInfo.GetValue(objectToMerge, null);

                if (toValue.HasValidValue(true))
                {
                    propertyInfo.SetValue(objCopy, toValue, null);
                }
            }

            return objCopy;
        }

        public static T Extend<T>(this T obj, NameValueCollection nameValueCollection)
        {
            //todo
            throw new NotImplementedException();
        }

        public static bool HasValidValue(this object obj, bool notEmptyString = false)
        {
            if (obj == null)
            {
                return false;
            }

            var t = obj.GetType();

            if (obj.Equals(t.GetDefault()))
            {
                return false;
            }

            if (notEmptyString && t == typeof(string))
            {
                return !string.IsNullOrEmpty(obj as string);
            }

            return true;
        }

        /// <summary>
        /// get the percentage of this number in the other
        /// </summary>
        /// <typeparam name="TIn">the type of input number</typeparam>
        /// <param name="piece"></param>
        /// <param name="whole"></param>
        /// <param name="fractionCount">the number of fractional digits in the return value</param>
        /// <returns></returns>
        public static double GetPercentage<TIn>(this TIn piece, TIn whole, int fractionCount = 0)
            where TIn : struct, IComparable, IFormattable, IConvertible
        {
            if (piece.IsZero()) //return 0 even if whole is 0, for this is getting a percentage.
            {
                return 0;
            }
            return Math.Round(piece.DivideBy<TIn, Double>(whole) * 100, fractionCount);
        }

        public static TOut DivideBy<TIn, TOut>(this TIn dividend, TIn divisor)
            where TIn : struct, IComparable, IFormattable, IConvertible 
            where TOut : struct, IComparable, IFormattable, IConvertible
        {
            var t = TypeConverter.ToType<Double>(dividend).ToNumber()/TypeConverter.ToType<Double>(divisor).ToNumber();
            return TypeConverter.ToType<TOut>(t.Value);
        }

        /// <summary>
        /// cast a NameValueCollection to a KeyValuePair
        /// </summary>
        /// <param name="collection"></param>
        /// <returns></returns>
        public static IEnumerable<KeyValuePair<string, string>> ToKeyValuePair(this NameValueCollection collection)
        {
            if (collection == null)
            {
                throw new ArgumentNullException("collection");
            }

            return collection.Cast<string>().Select(key => new KeyValuePair<string, string>(key, collection[key]));
        }


        /// <summary>
        /// Gets a IDictionary&lt;string, object> using specified <paramref name="entity"/>'s public-instance-filtered-properties
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="propertyNameFilter"></param>
        /// <param name="ignoreCaseInFilter"></param>
        /// <returns></returns>
        public static IDictionary<string, object> ToDictionary(this object entity, IEnumerable<string> propertyNameFilter, bool ignoreCaseInFilter = true)
        {
            var dic = new Dictionary<string, object>();

            foreach (var item in propertyNameFilter)
            {
                //var prop = PropertyContainer.GetPropertyInstancePublic<T>(item, ignoreCaseInFilter);
                var prop = entity.GetPropertyInstancePublic(item, ignoreCaseInFilter);
                dic.Add(item, prop == null ? string.Empty : prop.GetValue(entity, null));
            }

            return dic;
        }

        /// <summary>
        /// Gets a IDictionary&lt;string, object> using specified <paramref name="entity"/>'s public-instance-properties
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public static IDictionary<string, object> ToDictionary(this object entity)
        {
            return entity.GetPropertiesInstancePublic().ToDictionary(item => item.Name, item => item.GetValue(entity, null));
        }

        /// <summary>
        /// 以指定fields获取对象的hashcode
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="fields"></param>
        /// <returns></returns>
        public static int GetHashCode<T>(this T obj, params object[] fields)
        {
            return fields.Aggregate(17, (hashSoFar, field) => 23 * hashSoFar + (field ?? 0).GetHashCode());
        }

        /// <summary>
        /// 如果b为有效(默认的有效指b非default(T)且如果是字符串则需非空,如果指定了IsValid方法,则使用指定方法判断),则返回b,否则返回本身
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="a">调用者</param>
        /// <param name="b">相同类型的参数</param>
        /// <param name="isValid">参数是否有效的用户定义的判断方法</param>
        /// <returns>如果b为有效,则返回b,否则返回本身</returns>
        public static T GetValid<T>(this T a, T b, Func<T, bool> isValid = null)
        {
            if (isValid != null)
            {
                return isValid(b) ? b : a;
            }

            if (!Equals(default(T), b))
            {
                if (!string.IsNullOrEmpty(b.ToString()))
                {
                    return b;
                }
            }

            return a;
        }

        /// <summary>
        /// 如果b为有效(默认的有效指b有值且非default(T),如果指定了IsValid方法,则使用指定方法判断),则返回b,否则返回本身
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="a">调用者</param>
        /// <param name="b">相同类型的参数</param>
        /// <param name="isValid">参数是否有效的用户定义的判断方法</param>
        /// <returns>如果b为有效,则返回b,否则返回本身</returns>
        public static T GetValid<T>(this T a, T? b, Func<T, bool> isValid = null)
            where T : struct
        {
            if (b.HasValue)
            {
                if (isValid != null)
                {
                    return isValid(b.Value) ? b.Value : a;
                }

                if (!b.Value.Equals(default(T)))
                {
                    return b.Value;
                }
            }

            return a;
        }

        /// <summary>
        /// 如果b包含有效(默认的有效指b中元素有值且非default(T),如果指定了IsValid方法,则使用指定方法判断),则返回b中第一个有效的值,否则返回本身
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="isValid"></param>
        /// <returns></returns>
        public static T? GetValid<T>(this T? a, IEnumerable<T?> b, Func<T, bool> isValid = null)
           where T : struct
        {
            var set = b.Where(i => i.HasValue).Select(i => i.Value);

            if (isValid != null)
            {
                foreach (var item in set)
                {
                    if (isValid(item))
                    {
                        return item;
                    }
                }

                return a;
            }
            set = set.Where(i => !i.Equals(default(T)));

            return !set.Any() ? a : set.First();
        }

        /// <summary>
        /// 获取对象中非默认值的公共属性的数量,忽略名为Item的属性
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static int GetNondefaultPropCount<T>(this T obj)
        {
            return PropertyContainer.GetPropertiesInstancePublic<T>().Count(item => !Equals(item.GetValue(obj, null), item.PropertyType.GetDefault()));
        }

        public static IEnumerable<PropertyInfo> GetPropertiesInstancePublic(this object obj)
        {
            return PropertyContainer.GetPropertiesInstancePublic(obj.GetType());
        }

        public static PropertyInfo GetPropertyInstancePublic(this object obj, string name, bool ignoreCase = true)
        {
            return PropertyContainer.GetPropertyInstancePublic(obj.GetType(), name, ignoreCase);
        }

        /// <summary>
        /// convert <paramref name="value"/> to DayOfWeek
        /// </summary>
        /// <param name="value">value to convert</param>
        /// <returns>DayOfWeek</returns>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static DayOfWeek ToDayOfWeek(this object value)
        {
            if (value is DateTime)
            {
                return ((DateTime)value).DayOfWeek;
            }

            if (value.GetType().IsNumeric() || value.ToString().IsInteger())
            {
                value = Convert.ChangeType(value, EnumInternals<DayOfWeek>.UnderlyingType);

                if (value.Equals(7))
                {
                    value = 0;
                }
            }

            var tryRet = EnumHelper.TryParse<DayOfWeek>(value);

            if (tryRet.HasValue)
            {
                return tryRet.Value;
            }

            try
            {
                return DayOfWeekChinese.Single(i => value.ToString().Contains(i.Key)).Value;
            }
            catch
            {
                throw new ArgumentOutOfRangeException();
            }
        }

        //public static bool Equals<T>(this T obj, string propertyName, object value)
    }
}
